package com.opennews.openplatform.security

import com.opennews.openplatform.DateHelper
import groovy.time.TimeCategory
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys

@CompileStatic
class TokenManager {
    /**
     * Generates signed token.
     * @param claims: The input claims to generate token.
     * @param signingKey: The key to sign token
     * @param expiration: How long the token expires in seconds.
     * @return String of generated and signed token. AKA jws.
     */
    @CompileDynamic
    String generateToken(MyClaims claims, String signingKey, int expiration) {
        // Gets current date time for token issuedAt field.
        def currentTime = DateHelper.getCurrentDate()

        claims.issuedAt = currentTime.toInstant().epochSecond

        // Sets the expiresAt if needed.
        if (expiration > 0) {
            use(TimeCategory) {
                claims.expiresAt = (currentTime + expiration.seconds).toInstant().epochSecond
            }
        }

        // Prepares map data for jws token generation.
        def jwsClaims = [
            // Standard fields. Package uses these fields for parseClaimsJws method.
            // It will determine whether this token is valid and not expired.
            aud: claims.audience,
            exp: claims.expiresAt,
            iat: claims.issuedAt,
            iss: claims.issuer,
            nbf: claims.notBefore,
            sub: claims.subject,

            // Customized fields.
            accountGroupId: claims.accountGroupId,
            id: claims.id,
            username: claims.username,
            issuedAt: claims.issuedAt,
            expiresAt: claims.expiresAt,
            roles: claims.roles
        ]

        // Prepares sign key for jws token generation.
        def key = Keys.hmacShaKeyFor(signingKey.getBytes())

        // Generates token with provides claims.
        def jws = Jwts.builder().setClaims(jwsClaims).signWith(key).compact()

        // Returns generated an signed token.
        return jws
    }

    /**
     * Validates jws and returns BaseClaims instance.
     * @param jws: Generated and signed token.
     * @param signingKey: The key to unsign token.
     */
    ParsedToken validateToken(String jws, String signingKey) {
        def parsedToken = new ParsedToken()
        def key = Keys.hmacShaKeyFor(signingKey.getBytes())

        try {
            def jwsClaims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jws).body
            parsedToken.valid = true
            parsedToken.claims = new MyClaims()

            parsedToken.claims.with {
                accountGroupId = jwsClaims.accountGroupId
                id = jwsClaims.id
                username = jwsClaims.username
                issuedAt = jwsClaims.issuedAt as Long
                expiresAt = jwsClaims.expiresAt as Long
                roles = jwsClaims.roles as List<String>
            }
        } catch (Exception ex) {
            parsedToken.valid = false

            if (ex instanceof ExpiredJwtException) {
                parsedToken.expired = true
            }
        }

        return parsedToken
    }
}